/*
 * Copyright © 2021 http://www.hn-szzy.com/ All rights reserved.
 */
package com.xjh.basestudy.javabase.streamandlambda;


import org.apache.commons.lang3.tuple.Pair;
import org.apache.commons.lang3.tuple.Triple;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author： 徐建华
 * @date： 2022/5/9
 * @description：
 */
public class UnderstandSteam {
    public static void main(String[] args) {
        List<StudentBO> studentBOList = StudentBO.getStudentBOList();
        testFlatMap(studentBOList);
        collectingAndThen(studentBOList);
        testMapping(studentBOList);
        testComparing(studentBOList);


        // 处理不理解的地方
        Integer integer = testBase(1, studentBOList);
        System.out.println("testReturn" + integer);
        // 处理字符串（使用join floatMap等）
        handleStr(studentBOList);
        // 处理排序
        testSorted(studentBOList);
        // 处理去重
        collectingAndThen(studentBOList);
        // 处理对数据的分组
        listToMapAndGrouping(studentBOList);
        // 数据的比较及统计
        testCollectors(studentBOList);

        /**理解*/
        // 理解map 和 flatMap
        testMapAndFlatMap(studentBOList);

    }

    private static Integer testBase(Integer i, List<StudentBO> studentBOList) {
        Comparator<Integer> comparator = Integer::compare;
        int result = comparator.compare(100, 10);
        System.out.println("result1====" + result);
        // 接口名::方法名
        Comparator<Integer> comparator1 = LambdaInterface::compares;
        // 流程理解:
        // 1.stream流化
        // 2.map中得到list集合中每一个对象并处理
        // 3.收集
        StudentBO studentBO1 = new StudentBO();
        // StudentBO is not a functional interface 不是函数式接口
        // 函数式接口就是只定义一个抽象方法的接口
//        StudentBO studentBO2 = StudentBO::getStudentBOList;
//        List<Long> idList = studentBOList.stream().map((new StudentBO()).getId()).collect(Collectors.toList());

        studentBOList.stream().map(e -> e.getId()).collect(Collectors.toList());
        studentBOList.stream().map(StudentBO::getId).collect(Collectors.toList());

        if (i.equals(1)) {
            return 2;// 这里成立后后面的return就不会执行了
        }
        return 3;
    }

    private static void testCollectors(List<StudentBO> studentBOList) {
        // 根据性别分组，并将每个分组内的年龄累加起来（Collectors的summing和reducing功能类似）
        Map<Integer, Integer> collect1 = studentBOList.stream().collect(Collectors.groupingBy(StudentBO::getSex, Collectors.summingInt(StudentBO::getAge)));
        // 求平均数
        Map<Integer, Double> collect5 = studentBOList.stream().collect(Collectors.groupingBy(StudentBO::getSex, Collectors.averagingInt(StudentBO::getAge)));
        System.out.println("根据性别分组，并将每个分组内的年龄累加起来===" + collect1);
        // 计算集合内元素数量
        Long collect2 = studentBOList.stream().collect(Collectors.counting());
        // 获取对象的属性获取该属性最大或最小的对象
        Optional<StudentBO> collect3 = studentBOList.stream().collect(Collectors.maxBy(Comparator.comparingInt(StudentBO::getAge)));
        Optional<StudentBO> collect4 = studentBOList.stream().collect(Collectors.minBy(Comparator.comparingInt(StudentBO::getAge)));
        System.out.println("年纪最大" + collect3 + "年纪最小" + collect4);
        // 加个else就不是Optional类型了
        StudentBO studentBO = studentBOList.stream().min(Comparator.comparingInt(StudentBO::getAge)).orElse(new StudentBO());

        // 单类型元素集合(或者先用map过滤)
        List<Integer> integers = Arrays.asList(1, 2, 3);
        Integer min = integers.stream().reduce(integers.get(0), Integer::min);
        Integer max = integers.stream().reduce(0, Integer::max);
        System.out.println("minmin" + min);
        System.out.println("minmin" + max);
    }

    private static void testFlatMap(List<StudentBO> studentBOList) {
        Map<Long, String> setMealPayAmountMap = studentBOList.stream().map(e -> {

            return Pair.of(e.getId(), e.getName());
        }).collect(Collectors.toMap(Pair::getLeft, Pair::getRight));
        // Duplicate key 徐建华 这样搞如果有重复key，报错提示却是重复value
        System.out.println("setMealPayAmountMap" + setMealPayAmountMap);
        Integer a = 1;
        System.out.println(a.toString());

        List<StudentBO> studentBOList1 = StudentBO.getStudentBOList();
        // 将两个集合中的元素合并到一个新列表
        List<StudentBO> collect = Stream.of(studentBOList, studentBOList1).flatMap(Collection::stream).collect(Collectors.toList());
        System.out.println("flatmap===" + collect);
        // 假如我们有这样一个需求给定单词列表["Hello","World"]，你想要返回列表["H","e","l", "o","W","r","d"],
        List<String> nameList = studentBOList.stream()
                // map得到一个字段
                .map(StudentBO::getCharacter)
                // 处理每个性格字段：把所有性格字段以分号分割,存到一个数组中(不加这个，会直接返还一个字符串，现在是把一个字符串给分割了，元素变多了,一对多转换)
                // 将字符串分割成数组，然后将数组转成流，再将stream流进行flatMap处理成多个元素
                .flatMap(x -> Arrays.stream(x.split(";")))
                .distinct()
                .collect(Collectors.toList());
        System.out.println("取出所有种类性格===" + nameList);

    }

    private static void testSorted(List<StudentBO> studentBOList) {
        /**理解：
         *
         *
         * */
        // 1.单个字段排序(字符串类型也可以排序，保证都是数字即可)
        List<StudentBO> sorted = studentBOList.stream().sorted(Comparator
                .comparing(StudentBO::getId).reversed()).collect(Collectors.toList());
        System.out.println("根据id从大到小排列==" + sorted);
        // 2.多个字段排序
        List<StudentBO> sorted1 = studentBOList.stream()
                .sorted(
                        Comparator.comparing(StudentBO::getAge, Comparator.reverseOrder())
                                .thenComparing(StudentBO::getId, Comparator.reverseOrder())
                ).collect(Collectors.toList());
        System.out.println("先根据年龄从大到小排列，如果年龄相同，再根据id从大到小排列==" + sorted1);
        // 3. 多字段排序，错误写法
        List<StudentBO> sorted2 = studentBOList.stream()
                .sorted(
                        Comparator.comparing(StudentBO::getAge).reversed()
                                .thenComparing(StudentBO::getId).reversed()
                ).collect(Collectors.toList());
        System.out.println("先根据年龄从大到小排列，如果年龄相同，再根据id从大到小排列=======这样写法是错误的" + sorted2);
        String applyGoods = "212";
        Arrays.asList(applyGoods.split(","));
        System.out.println(Arrays.asList(applyGoods.split(",")).size());
        HashMap<Long, Object> objectObjectHashMap = new HashMap<>();
        objectObjectHashMap.put(32L, 12);
        /**简单数据的排序*/
        List<String> ids = Arrays.asList("205","10","308","49","627","193","111", "193");
        // 使用流操作
        List<Integer> sort = ids.stream()
                .distinct()
                // 将string 转为 integer
                .map(Integer::valueOf)
                .sorted(Comparator.comparingInt(o -> (int) o).reversed())
                // 最多要3个数据
                .limit(3)
                .collect(Collectors.toList());

        System.out.println("简单数据的排序（默认正序，从小到大）===="+sort);
        // 判断sort集合中是否有数据的值为205
        boolean b = sort.stream().anyMatch(Integer.valueOf("205")::equals);

        // 计算平均值
        Double average = sort.stream().collect(Collectors.averagingInt(value -> value));
        System.out.println("平均值：" + average);
        // 数据统计信息
        IntSummaryStatistics summary = sort.stream().collect(Collectors.summarizingInt(value -> value));

        Integer sum = sort.stream().reduce(Integer::sum).orElse(0);
        System.out.println("数据之和： " + summary);
        System.out.println("数据之和： " + sum);


        // 判断list 中对象 某个字段的值是否全部相同
        long count = studentBOList.stream().map(StudentBO::getAge).distinct().count();
        //判断（如果全部相等：count应该等于1）
        if (count != 1) {
            System.out.println("合并检验单料号必须相同");
        }


    }

    private static void collectingAndThen(List<StudentBO> studentBOList) {
        /**
         * 理解：
         *
         *
         * map小技巧：toMap再去重，不如直接toSet*/
        List<Object> collect = studentBOList.stream().map(e -> null).collect(Collectors.toList()); // 全是null
        System.out.println("collectingAndThen：testnull" + collect);
        List<Object> collect1 = studentBOList.stream().map(e -> e.getName()).map(e -> e.split("#")).flatMap(Arrays::stream).collect(Collectors.toList());
        System.out.println(collect1);
        /**过滤相同字段*/
        // 根据一个字段过滤：
        /*Collectors.collectingAndThen原理理解：先进行结果集的收集，然后将收集到的结果集进行下一步的处理：第一个参数得到set，然后转为第二个参数的类型ArrayList*/
       /* 过程：将集合放到TreeSet中，然后再将TreeSet转为List， 其中TreeSet要传入一个根据哪个属性进行比较的比较器，
        然后使用public ArrayList(Collection<? extends E> c)将TreeSet放入构造器中生成List*/
        /*结构：collect中包含了一个collectingAndThen、collectingAndThen中包含了两个参数：toCollection和ArrayList::new
       第一个:toCollection中包含了一个lambda表达式：创建了一个TreeSet集合，set包含comparing、comparing包含字段
       第二个：ArrayList::new */
        List<StudentBO> distinctByName = studentBOList.stream().collect(Collectors.collectingAndThen(
                Collectors.toCollection(
                        () -> new TreeSet<>(
                                Comparator.comparing(StudentBO::getName))
                ), ArrayList::new));
        System.out.println("根据姓名过滤===" + distinctByName);

        // 使用普通集合过滤掉某个相同的字段(作用同上)
        TreeSet<StudentBO> treeSet = new TreeSet<>(Comparator.comparing(StudentBO::getName));
        for (StudentBO studentBO : studentBOList) {
            treeSet.add(studentBO);
        }
        List<StudentBO> result2 = new ArrayList<>(treeSet);

        // 根据两个字段过滤:姓名和年龄（去掉年龄和姓名都重复的）
        Map<String, StudentBO> tempMap = new ConcurrentHashMap<>();
        List<StudentBO> distinctByNameAndAge = studentBOList
                .stream().filter(e -> {
                    // putIfAbsent() 方法会先判断指定的键（key）是否存在，不存在则将键/值对插入到 HashMap 中。
                    // 因为map原因，存在了相同的曾雪23，则tempMap中最终只有3条数据
                    // 小知识map的put方法的返回值是value，数据类型也是map中定义的value的类型
                    StudentBO studentBO = tempMap.putIfAbsent(e.getName() + e.getAge(), e);
                    // 问题：这个return 和对象为null是什么意思（filter里要return boolean）
                    // 答案：相当于continue关键字,结束当前这一次循环,进入下一轮循环:
                    // filter本来就必须返回boolean，根据条件留下返回值为true的元素、去除返回值为false的元素
                    //
                    return studentBO == null;
                }).collect(Collectors.toList());
        System.out.println("根据姓名和年龄过滤===" + distinctByNameAndAge);

    }

    private static void testComparing(List<StudentBO> studentBOList) {
        // 返回第一个符合条件的结果
        StudentBO studentBO = studentBOList.stream().findFirst().orElse(null);
        System.out.println("findFirst" + studentBO);
        // 随机返回一个符合条件的结果
        StudentBO studentBO1 = studentBOList.stream().findAny().orElse(null);
        System.out.println("findAny" + studentBO1);
    }

    private static void testMapping(List<StudentBO> studentBOList) {

        /**mapping:两个参数：（第一个参数：mapping得到数据、第二个参数：转为特定数据类型）
         * 通过参数mapper函数来处理List中的每一个数据，然后用downstream来将处理后的数据收集起来
         * 使用场景：得到原list集合中的一个或多个字段组成新的list（得到一个字段直接用map就好）*/
        Function<StudentBO, String> mapper = (e) -> {
            return e.getName();
        };
        List<String> userNames = studentBOList.stream().collect(Collectors.mapping(mapper, Collectors.toList()));
        System.out.println("mapping===" + userNames);
        // 简写流程
        List<String> userNames1 = studentBOList.stream().collect(Collectors.mapping((e) -> {
            return e.getName();
        }, Collectors.toList()));
        List<String> userNames2 = studentBOList.stream().collect(Collectors.mapping(StudentBO::getName, Collectors.toList()));
        List<String> userNames3 = studentBOList.stream().map(StudentBO::getName).collect(Collectors.toList());

        // 案例1：通过性别分组、value为name和age {"男":[Pair<"徐建华",“24”>]},然后就可以拿到该性别特定信息
        Map<Integer, List<Pair<String, Integer>>> collect = studentBOList.stream()
                .collect(Collectors.groupingBy(StudentBO::getSex, Collectors.mapping(e -> Pair.of(e.getName(), e.getAge()), Collectors.toList())));
        System.out.println("Mapping案例1：：：" + collect);

    }

    private static void listToMapAndGrouping(List<StudentBO> studentBOList) {

        /**理解：
         *
         *
         * */
        // 1. list 转 map 并设置key:id value:name （这里的value只是一个简单的字段）
        Map<Long, String> testGroupIdAndName = studentBOList.stream().collect(Collectors.toMap(StudentBO::getId, StudentBO::getName));
        System.out.println("根据id分组：简单valuetestGroupIdAndName===" + testGroupIdAndName);
        // list 转 map 并设置key:id value:StudentBO （这里必须保证key是唯一的，否则转map会报错）

        /**SELECT reel_id,count(*) as c FROM t_epv_feeder_rel

         group by reel_id having c>1;*/
        Map<Long, StudentBO> collect2 = studentBOList.stream().collect(Collectors.toMap(StudentBO::getId, Function.identity()));


        // 2. 分组直接将原来的list，根据某条件拆分出key(条件),value(list)的形式
        Map<Long, List<StudentBO>> collect = studentBOList.stream().collect(Collectors.groupingBy(StudentBO::getId));
        // 3.分组后，value类型是list，list中元素类型只取某一个字段id
        Map<Integer, List<Long>> collect1111111111 = studentBOList.stream().collect(Collectors.groupingBy(StudentBO::getSex,
                Collectors.mapping(e -> e.getId(), Collectors.toList())));
        System.out.println("testgrouping" + collect1111111111);
        // 4.根据性别分组，分组就是转map(value是数组，包含多字段)，key:sex /  value:list<Triple<>> （注意这里获取value时：用了Collectors.mapping，将value构建成一个list集合的类型，list中的数据类型是triple）
        Map<Integer, List<Triple<Long, String, Integer>>> goodsSetMealGroupMap = studentBOList.stream().collect(Collectors.groupingBy(StudentBO::getSex,
                Collectors.mapping(e -> Triple.of(e.getId(), e.getName(), e.getAge()), Collectors.toList())));
        System.out.println("根据性别分组，复杂value===" + goodsSetMealGroupMap);

        // 使用两个groupingBy：先通过性别分组成 key：sex  value:list<StudentBO> 再把value的list再次分组，key：age，value：list 所以最终是 Map<Integer, Map<Integer, List>> 的形式
        Map<Integer, Map<Integer, List<StudentBO>>> collect1 = studentBOList.stream().collect(Collectors.groupingBy(StudentBO::getSex, Collectors.groupingBy(StudentBO::getAge)));
        System.out.println("使用两个groupingBy==" + collect1);
        // 对转map的作用理解：通过匹配来避免双循环
        // 批量单循环版本（最好）：会员list里存在会员头像的id，如何批量得到头像的url
        // 1.批量取出头像id，得到一个list
        // 2.根据头像idlist批量查出所有头像，得到idAndUrlList
        // 3.将idAndUrlList转map key：id、 value：url
        // 4.循环会员list、根据其中的头像id，从map中得到url、url设置到会员对象
        // 原始双循环版本：
        //...得到idAndUrlList，循环会员列表、内层再循环idAndUrlList，如果会员列表中头像id 和 内循环中id相同，则把头像设置给该会员
        // 最垃圾版本：循环会员list，每条数据都查询一次数据库
     /*   List<Long> photoIdList = cartList.stream().map(MemberCartDTO::getPhoto).collect(Collectors.toList());
        List<FileInfoPO> photoUrlList = fileInfoDao.selectList(Wrappers.<FileInfoPO>lambdaQuery().in(FileInfoPO::getId, photoIdList));
        Map<Long, String> urlMap = photoUrlList.stream().collect(Collectors.toMap(FileInfoPO::getId, FileInfoPO::getUrl));
        cartList.forEach(e->{
            if(Objects.nonNull(e.getPhoto())){
                String photoUrl = urlMap.get(e.getPhoto());
                e.setPicUrl(photoUrl);
            }
        });*/
        //作用就是 根据表1 得到表2 根据表2 得到表3 最终关联 表1和表3
        // MemberVoucherListPO memberVoucherListPO = memberVoucherListPOMap.get(orderListDiscountPO.getVoucherNo());
        // 订单表中有订单号，根据订单号批量去查询disCount表中的订单使用的特权卡卡券号（转map ，key：订单号，value，特权卡卡券号）
        // 得到批量的卡券号，批量查询卡券表、得到批量的配置id（转map：key：卡券号 value：configid）
        // 循环订单表，根据orderNo 确定 卡券号，根据卡券号确定configid，最终得到一个订单所使用的特权卡。有么有优化方案
    }

    private static void handleStr(List<StudentBO> studentBOList) {
        /**join*/
        // join 将list中某个指定的属性连接成字符串
        String nameConnect = studentBOList.stream().map(StudentBO::getName).collect(Collectors.joining());
        // 将list中某个指定的属性连接成字符串并以逗号间隔
        String nameConnect1 = studentBOList.stream().map(StudentBO::getName).collect(Collectors.joining(","));
        // 简化直接string
        List<String> objects = new ArrayList<>();
        String.join(",", objects);

        // 将list中某个指定的属性连接成字符串并以逗号间隔，并设置前缀后缀
        String nameConnect2 = studentBOList.stream().map(StudentBO::getName).collect(Collectors.joining(",", "{", "}"));
        System.out.println(nameConnect);

        // 二次分割字符串得到list集合案例：先通过逗号分割，再通过#分割
        // 1484365116108894210#139680005813698560#1,1484367243896090625#139685416981757952#1
        String s = "1484365116108894210#139680005813698560#1,1484367243896090625#139685416981757952#1";
        List<String> goodsList = Arrays.asList(s.split(","));
        System.out.println(goodsList);
        goodsList.forEach(e -> {
            List<String> list = Arrays.asList(e.split("#"));
            // 获取订单某种商品的信息
            System.out.println(list.get(0));
        });
        /**原始：id#数量，id#数量  目的：得到所有id*/
        String m = "1484365116108894210#1,139680005813698560#2";
        List<String> collect1 = Arrays.stream(m.split(",")).collect(Collectors.toList());
        System.out.println("第一步：分开每个detailId和数量" + collect1);
        List<String> collect2 = collect1.stream().map(x -> x.split("#")[0]).collect(Collectors.toList());
        System.out.println("第二步：割除数量得到所有detailId" + collect2);
        List<String> collect3 = Arrays.stream(m.split(",")).map(x -> x.split("#")[0]).collect(Collectors.toList());
        System.out.println("优化：两步和为一步：" + collect3);
        // 那flatMap有啥用？ 可以把数量也展示出来，元素数量也给变掉了（两个变成4个），flatMap+Arrays.stream
        // 在上一步的基础上，如果不改变元素数量就用map，如果改变了元素数量就用flatMap!!!!!,因为map是从分割符号中选一个（比如0，1）所以，需要加后缀，而flatMap是全都要，所以不用加后缀
        List<String> collect4 = Arrays.stream(m.split(",")).flatMap(x -> Arrays.stream(x.split("#"))).collect(Collectors.toList());
        System.out.println("使用flatMap同时得到id和数量===" + collect4);

        // 假如我们有这样一个需求给定单词列表["Hello","World"]，你想要返回列表["H","e","l", "o","W","r","d"],
        List<String> nameList = studentBOList.stream()
                // map得到一个字段
                .map(StudentBO::getCharacter)
                // 处理每个性格字段：把所有性格字段以分号分割,存到一个数组中(不加这个，会直接返还一个字符串(“清纯;好看”)，现在是把一个字符串给分割了，元素变多了,一对多转换)
                // 将一个字符串分割成数组，然后将数组转成流，再将stream流进行flatMap处理成多个元素
                .flatMap(x -> Arrays.stream(x.split(";")))
                .distinct()
                .collect(Collectors.toList());
    }

    private static void testMapAndFlatMap(List<StudentBO> studentBOList) {
        /**map理解：对每个元素进行任意操作（永远不会改变元素数量）*/
        List<Map<String, Object>> list = new ArrayList<>();
        HashMap<String, Object> map1 = new HashMap<>();
        map1.put("name", "xujianhua");
        HashMap<String, Object> map2 = new HashMap<>();
        Object age1 = map2.put("age", 18);
        list.add(map1);
        list.add(map2);
        // 当前循环的对象经过大括号中的操作，返回每个循环的对象操作后对应的结果（所以元素的数量不会少）
        // 集合中当前位置的元素会被操作后的结果代替：比如e->e.getName ,会用getName代替当前对象，并生成集合（对于map来说箭头后就是返回的内容，对于filter来说，是过滤的结果）
        // 比如下面的return map，会用return的map代替当前位置的元素,为什么要这样做呢？通常情况下返回的内容会和循环的元素有一定关联
        List<HashMap<String, Object>> age = list.stream().map(e -> {
            HashMap<String, Object> map = new HashMap<>();
            map.put("name", e);
            return map;
        }).collect(Collectors.toList());
        System.out.println(age);

        /**map使用案例：现在有几个id，将其都set到StudentBO对象中：执行之后，会发现每一个元素都被转换为对应新的元素，但是前后总元素个数是一致的*/
        List<Long> ids = Arrays.asList(205L, 105L, 308L, 469L, 627L, 193L, 111L);
        List<StudentBO> results = ids.stream()
                .map(id -> {
                    StudentBO user = new StudentBO();
                    user.setId(id);
                    return user;
                })
                .collect(Collectors.toList());
        System.out.println("map作用：根据id集合批量生产对象，元素数量不变==="+results);

        /**flatMap使用案例：将两个句子的每个单词都变成一个元素：通过分割的方式改变list中的元素数量*/
        List<String> sentences = Arrays.asList("hello world","Jia Gou Wu Dao");
        // 使用流操作
        List<String> results1 = sentences.stream()
                .flatMap(sentence -> Arrays.stream(sentence.split(" ")))
                .collect(Collectors.toList());
        System.out.println("flatmap作用：把两个句子分割成多个单词，改变元素数量==="+results1);

        /**peek和foreach区别：peek是中间操作，没有count则不会执行，foreach是终止操作，可以执行,发现foreach不能var，因为foreach没有返回值，所以foreach适用于不需要返回值的时候操作*/
        sentences.stream().peek(System.out::println).count();
        sentences.stream().forEach(System.out::println);



    }
}
/**
 * cpu:1739（12700f）
 * 主板：988（华硕B660M重炮手wifi）
 * 显卡：5088（耕升3080）
 * 显示器：2488（优派vx2880-4k-pro-2）
 * 固态：529（铠侠rc20 1t）
 * 内存条：543（酷兽16gx2）
 * 电源：422（鑫谷850w）
 * 风冷:230（利民fs140）
 * 机箱：180（先马鲁班1）
 * cpu扣具硅脂：30
 * 显卡支架：15
 * 机箱风扇：200
 * 总价：11812元
 * <p>
 * 这个配置如何？第一次装机不太懂，还需要什么其他配件吗？
 */